package com.phamkhanh.common;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Fitness {

    private static Map<String, String> mathFunction = init();

  
    private StringBuffer[] suffixExps;

    public Fitness(int numVariables, int numEquals, String[] equals) {
        suffixExps = new StringBuffer[numEquals];
        String temp;
        for (int i = 0; i < numEquals; i++) {
            temp = normalize(equals[i]);
            temp = change(temp);
            suffixExps[i] = changeToSuffix(temp);
        }
    }

    public StringBuffer[] getSuffixExps() {
        return suffixExps;
    }

    public void setSuffixExps(StringBuffer[] suffixExps) {
        this.suffixExps = suffixExps;
    }
    
    

    private static Map<String, String> init() {
        Map<String, String> mathFunc = new HashMap<>();
        mathFunc.put("sin", "a");
        mathFunc.put("cos", "b");
        mathFunc.put("tan", "c");
        mathFunc.put("sinh", "d");
        mathFunc.put("cosh", "e");
        mathFunc.put("tanh", "f");
        mathFunc.put("asin", "g");
        mathFunc.put("acos", "h");
        mathFunc.put("atan", "i");
        mathFunc.put("log", "j");
        mathFunc.put("exp", "k");
        mathFunc.put("abs", "l");
        mathFunc.put("sqrt", "m");
        return mathFunc;
    }

    public static String normalize(final String str) {
        StringBuilder result = new StringBuilder();
        for (int i = 0; i < str.length(); i++) {
            if (str.charAt(i) != ' ') {
                result.append(str.charAt(i));
            }
        }
        System.out.println("after nomalize:"+result);
        return result.toString();
    }

    public static String change(String str) {
        String result = str;
        for (String key : mathFunction.keySet()) {
            Pattern p = Pattern.compile(key);
            Matcher m = p.matcher(result);
            result = m.replaceAll(mathFunction.get(key));
            System.out.println(key+":"+result);
        }
        System.out.println("After change:"+result);
        return result;
    }

    /**
     * Phuong thuc chuyen doi bieu thuc trung to sang hau to bang cach su dung
     * stack
     *
     * @param input Mot bieu thuc trung to chua toan hang, toan tu hai ngoi
     * (phep toan), va toan tu mot ngoi (ham)
     * @return Bieu thuc hau to
     */
    public static StringBuffer changeToSuffix(final String input) {
        StringBuffer result = new StringBuffer();

        Stack<String> stack = new Stack<>();
        StringBuffer number = new StringBuffer();

        boolean flag = false;

        for (int i = 0; i < input.length(); i++) {
            switch (input.charAt(i)) {
                case '0':
                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                case '8':
                case '9':
                case '.':
                    if (flag) {
                        number.append(String.valueOf(input.charAt(i)));
                        if ((i != input.length() - 1 && !(input.charAt(i + 1) <= '9' && input.charAt(i + 1) >= '0')) || i == input.length() - 1) {
                            flag = false;
                            result.append("x").append(number).append(" ");
                            number = new StringBuffer();
                        }
                        if (i == input.length() - 1 && !stack.isEmpty()) {
                            result.append(" ");
                        }
                        break;
                    }
                    result.append(String.valueOf(input.charAt(i)));
                    if (i != input.length() - 1 && !(input.charAt(i + 1) <= '9' && input.charAt(i + 1) >= '0') && input.charAt(i + 1) != '.') {
                        result.append(" ");
                    }
                    if (i == input.length() - 1 && !stack.isEmpty()) {
                        result.append(" ");
                    }
                    break;
                case '+':
                    if (!stack.isEmpty()) {
                        while (true) {
                            if (stack.isEmpty()) {
                                break;
                            }
                            String inStack = stack.peek();
                            if (mathFunction.containsValue(inStack) || inStack.equals("-") || inStack.equals("+") || inStack.equals("*") || inStack.equals("/") || inStack.equals("^")) {
                                result.append(stack.pop());
                                result.append(" ");
                            } else {
                                break;
                            }
                        }
                    }
                    stack.push("+");
                    break;
                case '-':                  
                    if ((i==0 )||(i > 0 && input.charAt(i - 1) == '(')) {
                        stack.push("*");
                        result.append("-1");
                        result.append(" ");
                        break;
                    }
                    if (!stack.isEmpty()) {
                        while (true) {
                            if (stack.isEmpty()) {
                                break;
                            }
                            String inStack = stack.peek();
                            if (mathFunction.containsValue(inStack) || inStack.equals("-") || inStack.equals("+") || inStack.equals("*") || inStack.equals("/") || inStack.equals("^")) {
                                result.append(stack.pop());
                                result.append(" ");
                                continue;
                            } else {
                                break;
                            }
                        }
                    }
                    stack.push("-");
                    break;
                case '*':
                    if (!stack.isEmpty()) {
                        while (true) {
                            if (stack.isEmpty()) {
                                break;
                            }
                            String inStack = stack.peek();
                            if (mathFunction.containsValue(inStack) || inStack.equals("*") || inStack.equals("/") || inStack.equals("^")) {
                                result.append(stack.pop());
                                result.append(" ");
                                continue;
                            } else {
                                break;
                            }
                        }
                    }
                    stack.push("*");
                    break;
                case '/':
                    if (!stack.isEmpty()) {
                        while (true) {
                            if (stack.isEmpty()) {
                                break;
                            }
                            String inStack = stack.peek();
                            if (mathFunction.containsValue(inStack) || inStack.equals("*") || inStack.equals("/") || inStack.equals("^")) {
                                result.append(stack.pop());
                                result.append(" ");
                                continue;
                            } else {
                                break;
                            }
                        }
                    }
                    stack.push("/");
                    break;
                case '^':
                    stack.push("^");
                    break;
                case '(':
                    stack.push("(");
                    break;
                case ')':
                    while (!stack.isEmpty() && !"(".equals(stack.peek())) {
                        result.append(stack.pop());
                        result.append(" ");
                    }
                    if (!stack.isEmpty() && "(".equals(stack.peek())) {
                        stack.pop();
                    }
                    break;
                case 'a':
                    stack.push("a");
                    break;
                case 'b':
                    stack.push("b");
                    break;
                case 'c':
                    stack.push("c");
                    break;
                case 'd':
                    stack.push("d");
                    break;
                case 'e':
                    stack.push("e");
                    break;
                case 'f':
                    stack.push("f");
                    break;
                case 'g':
                    stack.push("g");
                    break;
                case 'h':
                    stack.push("h");
                    break;
                case 'i':
                    stack.push("i");
                    break;
                case 'j':
                    stack.push("j");
                    break;
                case 'k':
                    stack.push("k");
                    break;
                case 'l':
                    stack.push("l");
                    break;
                case 'm':
                    stack.push("m");
                    break;
                case 'x':
                    flag = true;
                default:
            }
        }

        while (!stack.isEmpty()) {
            result.append(stack.pop());
            result.append(" ");
        }
        System.out.println("After change to suffix : "+result);
        return result;
    }

    public static float caculateFromSuffix(StringBuffer suffixExp, Map value) {
        Stack<Float> stack = new Stack<>();
        float result = 0.0f;
        StringTokenizer token = new StringTokenizer(suffixExp.toString());

        float num1, num2;
        while (token.hasMoreTokens()) {
            String temp = token.nextToken();
            if (value.containsKey(temp)) {
                stack.push((float) value.get(temp));
                continue;
            }
            if (isFloat(temp)) {
                stack.push(Float.parseFloat(temp));
                continue;
            }
            switch (temp) {
                case "+":
                    num1 = stack.pop();
                    num2 = stack.pop();
                    stack.push(num2 + num1);
                    break;
                case "-":
                    num1 = stack.pop();
                    num2 = stack.pop();
                    stack.push(num2 - num1);
                    break;
                case "*":
                    num1 = stack.pop();
                    num2 = stack.pop();
                    stack.push(num2 * num1);
                    break;
                case "/":
                    num1 = stack.pop();
                    num2 = stack.pop();
                    try {
                        stack.push(num2 / num1);
                    } catch (Exception e) {
                    }

                    break;
                case "^":
                    num1 = stack.pop();
                    num2 = stack.pop();
                    stack.push((float)Math.pow(num2, num1));
                    break;
                case "a":
                    num1 = stack.pop();
                    stack.push((float)Math.sin(num1));
                    break;
                case "b":
                    num1 = stack.pop();
                    stack.push((float)Math.cos(num1));
                    break;
                case "c":
                    num1 = stack.pop();
                    stack.push((float)Math.tan(num1));
                    break;
                case "d":
                    num1 = stack.pop();
                    stack.push((float)Math.sinh(num1));
                    break;
                case "e":
                    num1 = stack.pop();
                    stack.push((float)Math.cosh(num1));
                    break;
                case "f":
                    num1 = stack.pop();
                    stack.push((float)Math.tanh(num1));
                    break;
                case "g":
                    num1 = stack.pop();
                    stack.push((float)Math.asin(num1));
                    break;
                case "h":
                    num1 = stack.pop();
                    stack.push((float)Math.acos(num1));
                    break;
                case "i":
                    num1 = stack.pop();
                    stack.push((float)Math.atan(num1));
                    break;
                case "j":
                    num1 = stack.pop();
                    stack.push((float)Math.log(num1));
                    break;
                case "k":
                    num1 = stack.pop();
                    stack.push((float)Math.exp(num1));
                    break;
                case "l":
                    num1 = stack.pop();
                    stack.push(Math.abs(num1));
                    break;
                case "m":
                    num1 = stack.pop();
                    stack.push((float)Math.sqrt(num1));
                    break;

            }
        }
        return stack.pop();
    }

    public static boolean isFloat(String value) {
        try {
            Float.parseFloat(value);
            return true;
        } catch (Exception e) {
            return false;
        }
    }
    
    public int calculateSignFuntion(int j, float[] genes){
        Map<String, Float> map = new HashMap<>();
        for (int i = 0; i < genes.length; i++) {
            map.put("x" + i, genes[i]);
        }
        float temp =caculateFromSuffix(suffixExps[j], map);
        return ((temp >0) ? (1):(temp == 0 ? (0):-1));
    }

    /**
     * Tính giá trị fitness ứng với genes,
     * nếu trị tuyệt đối lớn nhất quá lớn (Double.POSITY_INFINITY), fitness sẽ bằng 0
     * Nếu trị tuyệt đối lớn nhất bằng 0 thì fitness sẽ lớn nhất và bằng 1
     * @param genes
     * @return 
     */
    public float calculate(float[] genes) {
        Map<String, Float> map = new HashMap<>();
        for (int i = 0; i < genes.length; i++) {
            map.put("x" + i, genes[i]);
        }
        float fitness = 0.0f;
        float temp;
        for (int i = 0; i < suffixExps.length; i++) {
           temp = Math.abs(caculateFromSuffix(suffixExps[i], map));
           if(fitness < temp ) fitness = temp;
        }
        return 1 / (fitness + 1);
    }

    @Override
    public String toString() {
        StringBuilder result = new StringBuilder("Fitness{");
        for (int i = 0; i < suffixExps.length; i++) {
            result.append(suffixExps[i]).append(",");
        }
        result.append("}");
        return result.toString();
    }
}
